﻿using UnityEngine;

namespace HeatMap
{
    [RequireComponent(typeof(MeshCollider))]
    public class HeatMapController : MonoBehaviour
    {
        private static int HeatMapLayer => LayerMask.NameToLayer("HeatMap");

        private const int MaxPointsCapacity = 32; // 最大容量常量

        private Material _material; // 材质
        private MeshRenderer _renderer; // 渲染器
        private float[] _points; // 热力点数组
        private int _hitCount; // 热力点数量

        private void Awake()
        {
            // 检查层级配置是否正确，确保当前物体在该层下
            if (HeatMapLayer < 0 || !gameObject.layer.Equals(HeatMapLayer))
            {
                Debug.LogError("HeatMap层配置错误，请确保层存在且当前物体在该层下。");
                return;
            }

            // 获取渲染器和材质
            _renderer = GetComponent<MeshRenderer>();
            _material = _renderer.material;
            _points = new float[3 * MaxPointsCapacity]; // 初始化热力点数组
        }

        /// <summary>
        /// 创建热力图数据，接受一个PointData数组作为参数。
        /// 清除现有数据并添加新的热力点。
        /// </summary>
        /// <param name="points">热力点数据数组</param>
        public void Create(params PointData[] points)
        {
            Clear(); // 清除现有数据

            // 检查传入的热力点数量是否超过最大容量
            if (points.Length > MaxPointsCapacity)
            {
                Debug.LogWarning("传入的热力点数量超过最大容量，将只处理前" + MaxPointsCapacity + "个点。");
            }

            int count = Mathf.Min(points.Length, MaxPointsCapacity);
            for (int i = 0; i < count; i++)
            {
                AddPoint(points[i]); // 添加新的热力点
            }
        }

        /// <summary>
        /// 清除当前的热力图数据。
        /// 创建一个新的空数组，并将_hitCount设置为0。
        /// 更新材质以显示清除后的效果。
        /// </summary>
        public void Clear()
        {
            _points = new float[3 * MaxPointsCapacity]; // 创建一个新的空数组
            _hitCount = 0; // 将_hitCount设置为0
            UpdateMaterial(); // 更新材质以显示清除后的效果
        }

        /// <summary>
        /// 添加新的热力点。
        /// 将点的Y坐标增加3，并向下发射射线以检测碰撞。
        /// 如果检测到碰撞，更新热力点数据。
        /// </summary>
        /// <param name="point">热力点数据</param>
        private void AddPoint(PointData point)
        {
            point.position.y += 3; // 将点的Y坐标增加3
            Ray ray = new Ray(point.position, Vector3.down); // 向下发射射线

            if (Physics.Raycast(ray, out var hitInfo, 10, 1 << HeatMapLayer)) // 检测碰撞
            {
                Vector2 uv = hitInfo.textureCoord; // 获取碰撞点的纹理坐标
                UpdatePointData(uv.x * 4 - 2, uv.y * 4 - 2, point.intensity); // 更新热力点数据
            }
        }

        /// <summary>
        /// 更新热力点数据。
        /// 将新的热力点数据添加到_points数组中，并更新_hitCount。
        /// 调用UpdateMaterial方法更新材质。
        /// </summary>
        /// <param name="xp">X坐标</param>
        /// <param name="yp">Y坐标</param>
        /// <param name="intensity">强度</param>
        private void UpdatePointData(float xp, float yp, float intensity)
        {
            // 检查热力点数组是否已满
            if (_hitCount >= MaxPointsCapacity)
            {
                Debug.LogWarning("热力点数组已达到最大容量，无法添加更多点。");
                return;
            }

            _points[_hitCount * 3] = xp; // 更新X坐标
            _points[_hitCount * 3 + 1] = yp; // 更新Y坐标
            _points[_hitCount * 3 + 2] = intensity; // 更新强度

            _hitCount++; // 增加_hitCount
            UpdateMaterial(); // 更新材质
        }

        /// <summary>
        /// 更新材质以显示热力图效果。
        /// 设置材质中的_Hits和_HitCount属性。
        /// </summary>
        private void UpdateMaterial()
        {
            _material.SetFloatArray("_Hits", _points); // 设置材质中的_Hits属性
            _material.SetInt("_HitCount", _hitCount); // 设置材质中的_HitCount属性
        }
    }
}
